module dungeon.mapper;

import std.array;

import nade.random;
import nade.ast;
import nade.loader;
import nade.vector;
import nade.region;

import dungeon.map;
import dungeon.tileset;


class Mapper {
	this()
	{
	}

	void generate(Map map, const Region2 region, uint steps)
	{
		for(int i = 0;  i < steps;  ++i)
		{
			auto coord = Vector2i(random(region.min.x, region.max.x), random(region.min.y, region.max.y));
			auto section = randomSection;
			if(section.check(map, coord))
				section.apply(map, map.tileset, coord);
		}
	}

	Section randomSection()
	{
		assert(_chanceTotal > 0);
		int r = random(0, _chanceTotal);
		foreach(section; _sections)
		{
			r -= section.chance;
			if(r < 0) return section;
		}

		assert(false);
	}

	string name() { return _name; }
	int depthMin() { return _depthMin; }
	int depthMax() { return _depthMax; }
	int chance() { return _chance; }

	protected {
		string		_name;
		int			_depthMin, _depthMax;
		int			_chance;

		Section[]	_sections;
		int			_chanceTotal;
	}

	static void install()
	{
		Loader!(Mapper).add("name",
			(ref Mapper mapper, AST ast, Object[string] state)
			{
				mapper._name = ast[0].as!string;
			}
		);

		Loader!(Mapper).add("depth",
			(ref Mapper mapper, AST ast, Object[string] state)
			{
				mapper._depthMin = ast[0].as!int;
				mapper._depthMax = ast[1].as!int;
			}
		);

		Loader!(Mapper).add("chance",
			(ref Mapper mapper, AST ast, Object[string] state)
			{
				mapper._chance = ast[0].as!int;
			}
		);

		Loader!(Mapper).add("section",
			(ref Mapper mapper, AST ast, Object[string] state)
			{
				auto section = new Section;
				load(section, ast, state);
				mapper._sections ~= section;
				mapper._chanceTotal += section.chance;
			}
		);
	}
}


class Section {
	alias bool function(Map.Tile tile) TileCheck;

	struct RegionTile {
		Region2		region;
		string		name;
	}
	struct CoordItem {
		Vector2i	coord;
		string		name;
	}
	struct CoordMonster {
		Vector2i	coord;
		string		name;
	}

	this()
	{
	}

	void addCheck(Region2 region, TileCheck check)
	{
		foreach(c; region) _checks[c] = check;
	}

	bool check(Map map, const Vector2i coord)
	{
		foreach(c, check; _checks)
		{
			auto tile = map.getTile(coord + c);
			if(tile.type is null  ||  !check(tile)) return false;
		}

		return true;
	}

	void apply(Map map, Tileset tileset, const Vector2i coord)
	{
		foreach(tile; _tiles) foreach(c; tile.region)
		{
			auto abscoord = coord + c;
			map.setTileType(abscoord, tileset.getTile(tile.name));
			map.clearTileOccupants(abscoord);
		}

		foreach(item; _items)
			map.game.getItemGroup(item.name).drop(map, coord + item.coord);

		foreach(monster; _monsters)
			map.game.getMonsterGroup(monster.name).spawn(map, coord + monster.coord);
	}

	int chance() { return _chance; }

	protected {
		int						_chance;
		TileCheck[Vector2i]		_checks;
		RegionTile[]			_tiles;
		CoordItem[]				_items;
		CoordMonster[]			_monsters;
	}

	static void install()
	{
		static bool checkEmpty(Map.Tile tile) { return tile.type.passable  &&  tile.occupants.keys.empty == 0; }
		static bool checkPassable(Map.Tile tile) { return tile.type.passable; }
		static bool checkImpassable(Map.Tile tile) { return !tile.type.passable; }
		static bool checkAny(Map.Tile tile) { return true; }

		
		Loader!(Section).add("chance",
			(ref Section section, AST ast, Object[string] state)
			{
				section._chance = ast[0].as!int;
			}
		);

		Loader!(Section).add("check",
			(ref Section section, AST ast, Object[string] state)
			{
				Region2 region;
				region.min.x = ast[1].as!int;
				region.min.y = ast[2].as!int;
				if(ast.length >= 5)
				{
					region.max.x = ast[3].as!int + 1;
					region.max.y = ast[4].as!int + 1;
				}
				else
				{
					region.max.x = region.min.x + 1;
					region.max.y = region.min.y + 1;
				}

				switch(ast[0].as!string)
				{
					case "passable": section.addCheck(region, &checkPassable); break;
					case "impassable": section.addCheck(region, &checkImpassable); break;
					case "any": section.addCheck(region, &checkAny); break;

					default:
				}
			}
		);

		Loader!(Section).add("tile",
			(ref Section section, AST ast, Object[string] state)
			{
				Region2 region;
				region.min.x = ast[1].as!int;
				region.min.y = ast[2].as!int;
				if(ast.length >= 5)
				{
					region.max.x = ast[3].as!int + 1;
					region.max.y = ast[4].as!int + 1;
				}
				else
				{
					region.max.x = region.min.x + 1;
					region.max.y = region.min.y + 1;
				}

				section._tiles ~= RegionTile(region, ast[0].as!string);
			}
		);

		Loader!(Section).add("item",
			(ref Section section, AST ast, Object[string] state)
			{
				Vector2i coord;
				coord.x = ast[1].as!int;
				coord.y = ast[2].as!int;

				section._items ~= CoordItem(coord, ast[0].as!string);
			}
		);

		Loader!(Section).add("monster",
			(ref Section section, AST ast, Object[string] state)
			{
				Vector2i coord;
				coord.x = ast[1].as!int;
				coord.y = ast[2].as!int;

				section._monsters ~= CoordMonster(coord, ast[0].as!string);
			}
		);
	}
}
